package com.fxl.majiang.room;

import com.fxl.majiang.enums.*;
import com.fxl.majiang.exception.CustomException;
import lombok.SneakyThrows;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author fengxinglei
 * @date 2022/8/1 17:10
 * @description 房间
 */
public class Room {

    // 房间id
    private String id;

    // 东风家
    private Player eastPlayer;

    // 南风家
    private Player southPlayer;

    // 西风家
    private Player westPlayer;

    // 北风家
    private Player northPlayer;

    // 初始化牌, 给每个用户分配起手手牌
    private Dealers dealers;

    // 用户选择的针对上一张打出的牌的操作, 比如吃、碰、胡、明杠等, key为userId, value为已选择的操作
    private ConcurrentHashMap<String, OperationOrderEnum> selectedOperatePlayedCardMap = new ConcurrentHashMap<>();

    // 用户能够操作桌上那张刚打出的牌的操作队列, 比如吃、碰、胡、明杠等, key为userId, value为可选择的操作
    private ConcurrentHashMap<String, List<OperationOrderEnum>> canOperatePlayedCardMap = new ConcurrentHashMap<>();

    public Room() {
        this.id = System.currentTimeMillis() + UUID.randomUUID().toString().replaceAll("-", "");
    }

    public String getId() {
        return id;
    }

    /**
     * 房间状态
     * @see RoomStatusEnum
     */
    private int status = RoomStatusEnum.PREPARING.getCode();

    /**
     * 是否是开始时正在补花
     */
    private boolean isSupplementWhenStart = false;

    /**
     * 上一位出牌的玩家userId
     */
    private String previousUserId = "";

    // 当前正在打牌的用户id`
    private String playingUserId = "";

    // 最近打出在牌桌的牌
    private TilesEnum lastTile;

    public String getPreviousUserId() {
        return previousUserId;
    }

    public boolean isSupplementWhenStart() {
        return isSupplementWhenStart;
    }

    public void setSupplementWhenStart(boolean supplementWhenStart) {
        isSupplementWhenStart = supplementWhenStart;
    }

    public String getPlayingUserId() {
        return playingUserId;
    }

    public void setPlayingUserId(String playingUserId) {
        this.playingUserId = playingUserId;
    }

    /**
     * 获取这个用户的点炮优先级 ，按照 东->南->西->北->东 这个顺序循环
     */
    public synchronized int getDianPaoOrder(String userId) {
        Player previousPlayer = getPlayer(previousUserId);
        // 东->南->西->北
        if (previousPlayer.getUserId().equals(eastPlayer.getUserId())) {
            if (southPlayer.getUserId().equals(userId)) {
                return 1;
            } else if (westPlayer.getUserId().equals(userId)) {
                return 2;
            } else if (northPlayer.getUserId().equals(userId)) {
                return 3;
            }
        }
        // 南->西->北->东
        else if (previousPlayer.getUserId().equals(southPlayer.getUserId())) {
            if (westPlayer.getUserId().equals(userId)) {
                return 1;
            } else if (northPlayer.getUserId().equals(userId)) {
                return 2;
            } else if (eastPlayer.getUserId().equals(userId)) {
                return 3;
            }
        }
        // 西->北->东->南
        else if (previousPlayer.getUserId().equals(westPlayer.getUserId())) {
            if (northPlayer.getUserId().equals(userId)) {
                return 1;
            } else if (eastPlayer.getUserId().equals(userId)) {
                return 2;
            } else if (southPlayer.getUserId().equals(userId)) {
                return 3;
            }
        }
        // 北->东->南->西
        else if (previousPlayer.getUserId().equals(northPlayer.getUserId())) {
            if (eastPlayer.getUserId().equals(userId)) {
                return 1;
            } else if (southPlayer.getUserId().equals(userId)) {
                return 2;
            } else if (westPlayer.getUserId().equals(userId)) {
                return 3;
            }
        }
        return 0;
    }

    /**
     * 清除操作队列数据
     */
    public synchronized void clearOperatePlayedCardMap() {
        selectedOperatePlayedCardMap = new ConcurrentHashMap<>();
        canOperatePlayedCardMap = new ConcurrentHashMap<>();
    }

    /**
     * 设置上一位玩家userId
     * @param userId
     */
    public synchronized void setPreviousUserId(String userId) {
        this.previousUserId = userId;
    }

    /**
     * 获取上一位玩家
     * @return
     */
    public Player previousPlayer() {
        return getPlayer(previousUserId);
    }

    // 初始化房间状态
    public synchronized void initStatus() {
        this.status = RoomStatusEnum.PREPARING.getCode();
    }

    public int getStatus() {
        return status;
    }

    /**
     * 是否结束了对上一张打出卡片的操作选择
     * @return
     */
    public boolean hasFinishOperatePlayedCard() {
        return canOperatePlayedCardMap.size() == selectedOperatePlayedCardMap.size();
    }

    /**
     * 判断自己的操作是否优先级最高
     * @return
     */
    public synchronized boolean isWinnerOfOperate(String userId) {
        // 是否大家都结束选择了，如果全部结束，则继续判断, 否则需要继续等待
        if (!hasFinishOperatePlayedCard()) {
            return false;
        }
        // 竞争获胜者userId
        String winnerUserId = null;
        // 上一个获胜者的操作
        OperationOrderEnum previousWinnerOperation = null;
        // 点炮列表
        List<String> dianPaoList = new ArrayList<>();
        for (String uid : selectedOperatePlayedCardMap.keySet()) {
            OperationOrderEnum operationOrder = selectedOperatePlayedCardMap.get(uid);
            // 判断是否放弃了
            if(operationOrder == OperationOrderEnum.GIVE_UP_OPERATION) {
                continue;
            }
            if (previousWinnerOperation == null) {
                previousWinnerOperation = operationOrder;
                winnerUserId = uid;
            }
            // order越小，越优先
            else if (operationOrder.getOrder() < previousWinnerOperation.getOrder()) {
                previousWinnerOperation = operationOrder;
                winnerUserId = uid;
            }
            if (operationOrder.getOperation() == OperationEnum.DIAN_PAO) {
                dianPaoList.add(uid);
            }
        }
        // 如果没有点炮胡，或者只有一个玩家点炮胡，直接判断此玩家是否是胜者
        if (CollectionUtils.isEmpty(dianPaoList) || dianPaoList.size() == 1) {
            return winnerUserId.equals(userId);
        }

        int winnerOrder = 0;
        // 处理点炮顺序
        for (String dianPaoUserId : dianPaoList) {
            int dianPaoOrder = getDianPaoOrder(dianPaoUserId);
            if (winnerOrder == 0) {
                winnerOrder = dianPaoOrder;
                winnerUserId = dianPaoUserId;
            }
            // order越小，优先级越高
            else if (dianPaoOrder < winnerOrder) {
                winnerOrder = dianPaoOrder;
                winnerUserId = dianPaoUserId;
            }
        }
        return winnerUserId.equals(userId);
    }

    /**
     * 选择了针对上一张打出的卡片的操作
     * @param userId
     * @param orderEnum
     */
    public synchronized boolean selectedOperatePlayedCard(String userId, OperationOrderEnum orderEnum) {
        if (CollectionUtils.isEmpty(selectedOperatePlayedCardMap)) {
            selectedOperatePlayedCardMap = new ConcurrentHashMap<>();
        }
        if (selectedOperatePlayedCardMap.containsKey(userId)) {
            return false;
        }
        selectedOperatePlayedCardMap.put(userId, orderEnum);
        return true;
    }

    /**
     * 加入能操作上一张打出的卡片的队列
     * @param userId
     * @param orderEnum
     */
    public synchronized void addToCanOperatePlayedCardMap(String userId, OperationOrderEnum orderEnum) {
        if (CollectionUtils.isEmpty(canOperatePlayedCardMap)) {
            canOperatePlayedCardMap = new ConcurrentHashMap<>();
        }
        List<OperationOrderEnum> orderList = canOperatePlayedCardMap.getOrDefault(userId, new ArrayList<>());
        orderList.add(orderEnum);
        canOperatePlayedCardMap.put(userId, orderList);
    }

    /**
     * 判断是否允许用户操作刚刚打的这张牌，这里是判断用户是否在允许操作的队列中
     * @param userId
     * @return
     */
    public synchronized boolean checkCanOperatePlayedCard(String userId, OperationOrderEnum orderEnum) {
        if (CollectionUtils.isEmpty(canOperatePlayedCardMap)) {
            return false;
        }
        List<OperationOrderEnum> orderList = canOperatePlayedCardMap.get(userId);
        if (CollectionUtils.isEmpty(orderList)) {
            return false;
        }
        return orderList.contains(orderEnum);
    }

    /**
     * 获取发牌器
     * @return
     */
    public Dealers getDealers() {
        return dealers;
    }

    /**
     * 获取庄家userId
     * @return
     */
    public String getBankerUserId() {
        if (eastPlayer != null) {
            return eastPlayer.getUserId();
        }
        return null;
    }

    public synchronized void setLastTile(TilesEnum lastTile) {
        this.lastTile = lastTile;
    }

    // 是否最近一张牌
    public boolean isLastTile(TilesEnum tile) {
        return lastTile == tile;
    }

    public TilesEnum getLastTile() {
        return lastTile;
    }

    // 清除最近在桌上的牌
    public synchronized void clearLastTile() {
        this.lastTile = null;
    }

    /**
     * 判断是否当前用户
     * @param userId
     * @return
     */
    public boolean isCurPlayer(String userId) {
        return this.playingUserId.equals(userId);
    }

    /**
     * 是否是下一个用户
     * @param userId
     * @return
     */
    public boolean isNextPlayer(String userId) {
        Player nextPlayer = getNextPlayer();
        return userId.equals(nextPlayer.getUserId());
    }

    /**
     * 加入房间
     * @param userId
     * @return 房间id
     */
    @SneakyThrows
    public synchronized String join(String userId) {
        this.distribution(userId);
        Player player = getPlayer(userId);
        player.setPlayStatus(PlayerStatusEnum.UN_READY);
        return id;
    }

    // 判断是否坐满了
    private boolean isTtFull() {
        return eastPlayer != null && southPlayer != null && westPlayer != null && northPlayer != null;
    }

    // 判断是否坐满了
    private boolean isTtEmpty() {
        return eastPlayer == null && southPlayer == null && westPlayer == null && northPlayer == null;
    }

    /**
     * 分配位置,按照东南西北顺序分配
     * @param userId
     * @return
     */
    @SneakyThrows
    private synchronized void distribution(String userId) {
        if (eastPlayer == null) {
            eastPlayer = new Player(userId, SeatEnum.EAST);
            return;
        } else if (southPlayer == null) {
            southPlayer = new Player(userId, SeatEnum.SOUTH);
            return;
        } else if (westPlayer == null) {
            westPlayer = new Player(userId, SeatEnum.WEST);
            return;
        } else if (northPlayer == null) {
            northPlayer = new Player(userId, SeatEnum.NORTH);
            return;
        }
        throw new CustomException("房间已满员");
    }

    /**
     * 开始
     */
    @SneakyThrows
    public synchronized void start() {
        if (this.status == RoomStatusEnum.IN_PROGRESS.getCode()) {
            throw new CustomException("对局进行中");
        }
        clearLastTile();
        clearOperatePlayedCardMap();
        this.status = RoomStatusEnum.IN_PROGRESS.getCode();
        // 初始化牌, 给每个用户分配起手手牌
        dealers = new Dealers();
        eastPlayer.init();

        southPlayer.init();
        westPlayer.init();
        northPlayer.init();

        eastPlayer.setOwnedTiles(dealers.getEveryStarting());
        southPlayer.setOwnedTiles(dealers.getEveryStarting());
        westPlayer.setOwnedTiles(dealers.getEveryStarting());
        northPlayer.setOwnedTiles(dealers.getEveryStarting());
        // 庄家多一个头子
        eastPlayer.grabbing(dealers.getSingle());

        // 判断东风家是否有补花
        if (canBuHua(eastPlayer.getOwnedHuaTiles(), dealers.getVersatileTiles())) {
            // 东风家优先补花
            this.playingUserId = eastPlayer.getUserId();
            this.isSupplementWhenStart = true;
        } else {
            // 如果东风家没得补花，则判断下边有没有可以补花的，如果有，则让他补
            String nextSupplementUserId = nextSupplementWhenStart(eastPlayer.getUserId());
            if (StringUtils.hasText(nextSupplementUserId)) {
                this.playingUserId = nextSupplementUserId;
                this.isSupplementWhenStart = true;
            }
            // 如果所有人都没得补，则东风家先出牌
            else {
                this.playingUserId = eastPlayer.getUserId();
                this.isSupplementWhenStart = false;
            }
        }
    }

    private boolean canBuHua(List<TilesEnum> ownedHuaTiles, List<TilesEnum> versatileTiles) {
        // 如果有花并且花牌不是百搭，则可以补
        for (TilesEnum tile : ownedHuaTiles) {
            if (!versatileTiles.contains(tile)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 离开房间
     * @param userId
     */
    public synchronized void leave(String userId) {
        // 移除用户队列
        removePlayer(userId);
    }

    /**
     * 校验是否可以加入
     * @return
     */
    public boolean canJoin() {
        // 如果未满员则可以加入
        return !isTtFull();
    }

    /**
     * 东南西北家是否都准备了
     * @return
     */
    public boolean isAllReady() {
        return eastPlayer != null && eastPlayer.getPlayStatus() == PlayerStatusEnum.READY
                && southPlayer != null && southPlayer.getPlayStatus() == PlayerStatusEnum.READY
                && westPlayer != null && westPlayer.getPlayStatus() == PlayerStatusEnum.READY
                && northPlayer != null && northPlayer.getPlayStatus() == PlayerStatusEnum.READY;

    }

    /**
     * 获取是哪个玩家信息
     * @param userId
     * @return
     */
    public synchronized Player getPlayer(String userId) {
        if (eastPlayer != null && eastPlayer.getUserId().equals(userId)) {
            return eastPlayer;
        } else if (southPlayer != null && southPlayer.getUserId().equals(userId)) {
            return southPlayer;
        } else if (westPlayer != null && westPlayer.getUserId().equals(userId)) {
            return westPlayer;
        } else if (northPlayer!= null && northPlayer.getUserId().equals(userId)) {
            return northPlayer;
        }
        return null;
    }

    public synchronized Player getPlayingPlayer() {
        return getPlayer(this.playingUserId);
    }

    /**
     * 获取下一位出牌者
     * @return
     */
    public synchronized Player getNextPlayer() {
        // 如果是东风家，则下家是南风家, 按照东南西北的顺序分配
        if (eastPlayer.getUserId().equals(playingUserId)) {
            return southPlayer;
        } else if (southPlayer.getUserId().equals(playingUserId)) {
            return westPlayer;
        } else if (westPlayer.getUserId().equals(playingUserId)) {
            return northPlayer;
        } else if (northPlayer.getUserId().equals(playingUserId)) {
            return eastPlayer;
        }
        return null;
    }

    /**
     * 获取这个用户的下家
     * @return
     */
    public synchronized Player getThisUserNextPlayer(String userId) {
        // 如果是东风家，则下家是南风家, 按照东南西北的顺序分配
        if (eastPlayer.getUserId().equals(userId)) {
            return southPlayer;
        } else if (southPlayer.getUserId().equals(userId)) {
            return westPlayer;
        } else if (westPlayer.getUserId().equals(userId)) {
            return northPlayer;
        } else if (northPlayer.getUserId().equals(userId)) {
            return eastPlayer;
        }
        return null;
    }

    /**
     * 获取开始时第一个可以补花的，从东家开始
     * @return
     */
    public synchronized String firstSupplementWhenStart() {
        String userId = null;
        if (TilesEnum.haveHuaOrBaiBan(eastPlayer.getOwnedTiles())) {
            userId = eastPlayer.getUserId();
        } else if (TilesEnum.haveHuaOrBaiBan(southPlayer.getOwnedTiles())) {
            userId = southPlayer.getUserId();
        } else if (TilesEnum.haveHuaOrBaiBan(westPlayer.getOwnedTiles())) {
            userId = westPlayer.getUserId();
        } else if (TilesEnum.haveHuaOrBaiBan(northPlayer.getOwnedTiles())) {
            userId = northPlayer.getUserId();
        }
        if (userId == null) {
            this.setSupplementWhenStart(false);
        }
        return userId;
    }

    /**
     * 一家家轮流找下去，判断是否有下家可以起手补花
     * @param userId
     * @return
     */
    public synchronized String nextSupplementWhenStart(String userId) {
        String curUserId = userId;
        // 一家家轮流找，最后轮到自己，因为自己补完之后可能还有花
        for (int i=0; i<4; i++) {
            Player thisUserNextPlayer = getThisUserNextPlayer(curUserId);
            boolean canBuHua = false;
            // 判断拥有的花牌是否有可以补的，如果不是百搭牌则可以补
            for (TilesEnum tile : thisUserNextPlayer.getOwnedHuaTiles()) {
                if (!this.dealers.isVersatile(tile)) {
                    canBuHua = true;
                    break;
                }
            }
            String thisUserId = thisUserNextPlayer.getUserId();
            if (canBuHua) {
                return thisUserId;
            }
            curUserId = thisUserId;
        }
        return null;
    }

    /**
     * 下一个用户
     */
    public synchronized String nextPlayer() {
        Player nextPlayer = getNextPlayer();
        this.playingUserId = nextPlayer.getUserId();
        return this.playingUserId;
    }

    /**
     * 移除某个用户
     * @param userId
     */
    private void removePlayer(String userId) {
        if (eastPlayer!= null && eastPlayer.getUserId().equals(userId)) {
            eastPlayer = null;
        } else if (southPlayer!= null && southPlayer.getUserId().equals(userId)) {
            southPlayer = null;
        } else if (westPlayer!= null && westPlayer.getUserId().equals(userId)) {
            westPlayer = null;
        } else if (northPlayer!=null && northPlayer.getUserId().equals(userId)) {
            northPlayer = null;
        }
    }

    /**
     * 获取所有玩家
     * @return
     */
    public List<Player> getAllPlayers() {
        List<Player> list = new ArrayList<>();
        if (eastPlayer != null) {
            list.add(eastPlayer);
        }
        if (southPlayer != null) {
            list.add(southPlayer);
        }
        if (westPlayer != null) {
            list.add(westPlayer);
        }
        if (northPlayer != null) {
            list.add(northPlayer);
        }
        return list;
    }

    /**
     * 获取所有玩家userIds
     * @return
     */
    public List<String> getAllUserIds() {
        List<String> list = new ArrayList<>();
        if (eastPlayer != null) {
            list.add(eastPlayer.getUserId());
        }
        if (southPlayer != null) {
            list.add(southPlayer.getUserId());
        }
        if (westPlayer != null) {
            list.add(westPlayer.getUserId());
        }
        if (northPlayer != null) {
            list.add(northPlayer.getUserId());
        }
        return list;
    }

    /**
     * 获取用户所在的位子
     * @param userId
     * @return
     */
    public SeatEnum getSeat(String userId) {
        return getPlayer(userId).getSeat();
    }

}
